package com.zhou.seckill.controller;

import com.alibaba.fastjson.JSON;
import com.google.common.util.concurrent.RateLimiter;
import com.zhou.seckill.access.AccessLimit;
import com.zhou.seckill.domain.SeckillOrder;
import com.zhou.seckill.domain.SeckillUser;
import com.zhou.seckill.rabbitmq.MQSender;
import com.zhou.seckill.rabbitmq.SeckillMessage;
import com.zhou.seckill.redis.AccessKey;
import com.zhou.seckill.redis.GoodsKey;
import com.zhou.seckill.redis.OrderKey;
import com.zhou.seckill.redis.SeckillUserKey;
import com.zhou.seckill.result.CodeMsg;
import com.zhou.seckill.result.Result;
import com.zhou.seckill.result.SecKillCode;
import com.zhou.seckill.service.GoodsService;
import com.zhou.seckill.service.OrderService;
import com.zhou.seckill.service.RedisService;
import com.zhou.seckill.service.SeckillService;
import com.zhou.seckill.vo.GoodsVo;

import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/seckill")
public class SeckillController implements InitializingBean {

    @Autowired
    private SeckillService seckillService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private OrderService orderService;

    @Autowired
    private MQSender sender;

    private Log logger = LogFactory.getLog(SeckillController.class);

    //基于令牌桶算法的限流实现类
    private RateLimiter rateLimiter = RateLimiter.create(10);

    private HashMap<Long, Boolean> localOverMap = new HashMap<Long, Boolean>();

    @Override
    public void afterPropertiesSet() {
        List<GoodsVo> goodsList = goodsService.listGoodsVo();
        if (goodsList == null) {
            return;
        }
        for (GoodsVo goods : goodsList) {
            redisService.set(GoodsKey.getSeckillGoodsStock, String.valueOf(goods.getId()), goods.getStockCount());
            localOverMap.put(goods.getId(), false);
        }
    }

    @RequestMapping(value = "/{path}/do_seckill", method = RequestMethod.POST)
    @ResponseBody
    public Result<Integer> seckill(String accessToken, String promoId, SeckillUser user,
                                   @RequestParam("skuId") long skuId,
                                   @PathVariable("path") String path) {
        if (!rateLimiter.tryAcquire(1000, TimeUnit.MILLISECONDS)) {
            return Result.error(CodeMsg.ACCESS_LIMIT_REACHED);
        }
        Boolean flag = isAppointed(accessToken, promoId).getData();
        if (!flag) {
            return Result.secKillError(SecKillCode.PROMO_NOT_SUBSCRIBE);
        }

        GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(skuId);

        if (System.currentTimeMillis() > goodsVo.getEndDate().getTime()) {
            return Result.secKillError(SecKillCode.PROMO_NOT_FOUND);
        }

        if (redisService.exists(SeckillUserKey.userKey, user.getId() + "_isFinished")) {
            return Result.secKillError(SecKillCode.ORDER_OUT_OF_LIMIT);
        }
//    //验证path
      /*  boolean check = seckillService.checkPath(user, skuId, path);
        if (!check) {
            return Result.error(CodeMsg.REQUEST_ILLEGAL);
        }*/

        //内存标记，减少redis访问
        boolean over = localOverMap.get(skuId);
        if (over) {
            return Result.secKillError(SecKillCode.WARE_OUT_OF_STOCK);
        }

        //预减库存
        long stock = redisService.decr(GoodsKey.getSeckillGoodsStock, String.valueOf(skuId));
        if (stock < 0) {
            localOverMap.put(skuId, true);
            return Result.secKillError(SecKillCode.WARE_OUT_OF_STOCK);
        }

        redisService.set(SeckillUserKey.userKey, user.getId() + "_isFinished", user.getId());
//        redisService
//                .set(OrderKey.getSeckillOrderByUidAndGid, "" + user.getId() + "_" + skuId, SeckillOrder.class);
        //判断是否已经秒杀到了
        SeckillOrder order = orderService.getSeckillOrderByUserIdAndGoodsId(user.getId(), skuId);
        if (order != null) {
            return Result.error(CodeMsg.REPEATE_SECKILL);
        }
//        GoodsVo goods = goodsService.getGoodsVoByGoodsId(skuId);
//    int stock = goods.getStockCount();
//    if (stock <= 0) {
//      return;
//    }

        //判断是否已经秒杀到了
        //由于已经加唯一索引,可以不作判断
//    SeckillOrder order = orderService.getSeckillOrderByUserIdAndGoodsId(user.getId(), goodsId);
//    if (order != null) {
//      return;
//    }
        //减库存 下订单 写入秒杀订单
//        seckillService.seckill(user, goods);
        //入队
    SeckillMessage seckillMessage = new SeckillMessage();
    seckillMessage.setUser(user);
    seckillMessage.setGoodsId(skuId);
    sender.sendSeckillMessage(seckillMessage);
        return Result.success(0);//排队中
    }

    /**
     * orderId：成功 -1：秒杀失败 0： 排队中
     */
    @RequestMapping(value = "/result", method = RequestMethod.GET)
    @ResponseBody
    public Result<Long> seckillResult(Model model, SeckillUser user,
                                      @RequestParam("goodsId") long goodsId) {
        model.addAttribute("user", user);
        if (user == null) {
            return Result.error(CodeMsg.SESSION_ERROR);
        }
        long result = seckillService.getSeckillResult(user.getId(), goodsId);
        return Result.success(result);
    }

    @AccessLimit(seconds = 5, maxCount = 5)
    @RequestMapping(value = "/path", method = RequestMethod.GET)
    @ResponseBody
    public Result<String> getSeckillPath(HttpServletRequest request, SeckillUser user,
                                         @RequestParam("skuId") long skuId) {
        if (user == null) {
            return Result.error(CodeMsg.SESSION_ERROR);
        }

//        boolean check = seckillService.checkVerifyCode(user, goodsId, verifyCode);
//        if (!check) {
//            return Result.error(CodeMsg.REQUEST_ILLEGAL);
//        }

        String path = seckillService.createSeckillPath(user, skuId);
        return Result.success(path);
    }

    @RequestMapping(value = "/verifyCode", method = RequestMethod.GET)
    @ResponseBody
    public Result<String> getSeckillVerifyCode(HttpServletResponse response, SeckillUser user,
                                               @RequestParam("goodsId") long goodsId) {
        if (user == null) {
            return Result.error(CodeMsg.SESSION_ERROR);
        }
        try {
            BufferedImage image = seckillService.createVerifyCode(user, goodsId);
            OutputStream out = response.getOutputStream();
            ImageIO.write(image, "JPEG", out);
            out.flush();
            out.close();
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error(CodeMsg.SECKILL_FAIL);
        }
    }

    @PostMapping("/subscribe")
    @ResponseBody
    public Result<String> appointment(String accessToken, String promoId) {
//    redisService.get()
        SeckillUser userToken = redisService.get(SeckillUserKey.token, accessToken, SeckillUser.class);
        if (userToken == null) {
            return Result.error(CodeMsg.REQUEST_ILLEGAL);
        }

//        AccessKey accessKey = AccessKey.withExpire(ACTIVITY_EXPIRED_TIME);
        try {
            SeckillUser seckillUser = redisService.get(SeckillUserKey.subscribeKey, userToken.getId() + "_" + promoId, SeckillUser.class);
            if (seckillUser != null) {
                return Result.success("该用户已经预约，无需再次预约...");
            }
            boolean appointResult = redisService.set(SeckillUserKey.subscribeKey, userToken.getId() + "_" + promoId, userToken);
            if (appointResult) {
                return Result.success("预约成功！");
            }
        } catch (Exception e) {
            logger.error("redis连接出错，预约失败！user=" + JSON.toJSONString(userToken));
        }
        return Result.success("预约失败！");
    }

    @GetMapping("/isAppointed")
    @ResponseBody
    public Result<Boolean> isAppointed(String accessToken, String promoId) {
        SeckillUser userToken = redisService.get(SeckillUserKey.token, accessToken, SeckillUser.class);
        if (userToken == null) {
            return Result.error(CodeMsg.REQUEST_ILLEGAL);
        }
        try {
            SeckillUser seckillUser = redisService.get(SeckillUserKey.subscribeKey, userToken.getId() + "_" + promoId, SeckillUser.class);
            if (seckillUser != null) {
                return Result.success(true);
            }
        } catch (Exception e) {
            logger.error("redis连接出错，预约失败！user=" + JSON.toJSONString(userToken));
        }
        return Result.success(false);
    }

//    public Result<Long>

}
